using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.IO;
using WbsEditor.Generation;
using WbsEditor.MsProject2007;
using System.Xml.Serialization;

namespace WbsEditor
{
    public class PersonWorkload
    {
        public PersonWorkload(string name)
        {
            _name = name;
        }
        private string _name = "";
        public string Name { get { return _name; } }
        private float _remainingWork;
        public float RemainingWork { get { return _remainingWork; } set { _remainingWork = value; } }
        private float _completedWork;
        public float CompletedWork { get { return _completedWork; } set { _completedWork = value; } }
        private int _tasksAssigned;
        public int TasksAssigned { get { return _tasksAssigned; } set { _tasksAssigned = value; } }

        public bool IsEmpty()
        {
            return (TasksAssigned == 0 &&
                Math.Abs(RemainingWork) < 0.004f &&
                Math.Abs(CompletedWork) < 0.004f);
        }
    }

    class WbsExporter
    {
        private WbsExporter()
        {
        }

        #region Public static methods
        public static void CopyTaskListToClipboard(WorkBreakdownStructure wbs)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("\"Work Item Type\"\t\"State\"\t\"Priority\"\t\"AssignedTo\"\t\"Title\"\t\"Remaining Work\"\t\"Completed Work\"\t\"Iteration Path\"\n");
            ExportTaskListToCsv(wbs, wbs.Root, sb);

            Clipboard.SetText(sb.ToString());
        }

        private static void ExportTaskListToCsv(WorkBreakdownStructure wbs, WbsNode node, StringBuilder sb)
        {
            if (node.IsLeaf)
            {
                sb.AppendFormat("\"{0}\"\t\"{1}\"\t\"{2}\"\t\"{3}\"\t\"{4}\"\t{5}\t{6}\t\"{7}\"\n",
                    "Task",
                    string.IsNullOrEmpty(node.State) ? "Active" : node.State,
                    node.Priority == 0 ? 1 : node.Priority,
                    node.AssignedTo,
                    BuildTitle(wbs, node),
                    node.RemainingWorkInDays,
                    node.CompletedWorkInDays,
                    string.IsNullOrEmpty(node.Iteration) ? "/" : node.Iteration);
            }
            else
            {
                foreach (WbsNode childNode in node.ChildNodes)
                {
                    ExportTaskListToCsv(wbs, childNode, sb);
                }
            }
        }

        public static string BuildTitle(WorkBreakdownStructure wbs, WbsNode node)
        {
            string workPackagePrefix = WbsEditor.Properties.Settings.Default.WorkPackagePrefix;
            if (!node.IsRoot)
            {
                string title = string.Format("{0}: {1}{2} - {3}",
                    wbs.TID, workPackagePrefix, node.PspCode, node.Title);
                return title;
            }
            else
            {
                string title = string.Format("(WBS Editor) {0}: {1}",
                    wbs.TID, node.Title);
                return title;
            }
        }

        public static string ExtractTitle(string text, bool isRoot)
        {
            if (!isRoot)
            {
                int indexOfColon = text.IndexOf(':');
                if (indexOfColon >= 0)
                {
                    int indexOfHyphen = text.IndexOf('-', indexOfColon);
                    if (indexOfHyphen > 0)
                    {
                        string title = text.Substring(indexOfHyphen + 1).TrimStart();
                        return title;
                    }
                }
            }
            else
            {
                int indexOfColon = text.IndexOf(':');
                if (indexOfColon >= 0)
                {
                    string title = text.Substring(indexOfColon + 1).TrimStart();
                    return title;
                }
            }
            return text;
        }

        public static void CopyWorkloadToClipboard(WbsNode wbsNode)
        {
            PersonWorkload[] workloads = ExtractWorkload(wbsNode);

            StringBuilder sb = new StringBuilder();
            sb.Append("\"AssignedTo\"\t\"No of Tasks\"\t\"Remaining Work\"\t\"Completed Work\"\n");
            foreach (PersonWorkload pw in workloads)
            {
                sb.AppendFormat("\"{0}\"\t{1}\t{2}\t{3}\n",
                    pw.Name,
                    pw.TasksAssigned,
                    pw.RemainingWork,
                    pw.CompletedWork);
            }
            Clipboard.SetText(sb.ToString());
        }

        public static PersonWorkload[] ExtractWorkload(WbsNode wbsNode)
        {
            return ExtractWorkload(wbsNode, new string[] { });
        }

        public static PersonWorkload[] ExtractWorkload(WbsNode wbsNode, string[] iterations)
        {
            Dictionary<string, PersonWorkload> workloadDictionary = new Dictionary<string, PersonWorkload>();
            ExtractWorkload(wbsNode, workloadDictionary, iterations);
            List<string> names = new List<string>();
            foreach (string name in workloadDictionary.Keys)
            {
                names.Add(name);
            }
            names.Sort();
            List<PersonWorkload> loadList = new List<PersonWorkload>();
            foreach (string name in names)
            {
                PersonWorkload pw = workloadDictionary[name];
                if (pw.Name != "(unassigned)"
                    || !pw.IsEmpty())
                {
                    loadList.Add(pw);
                }
            }
            return loadList.ToArray();
        }

        public static bool ExportWbsAsImage(WorkBreakdownStructure wbs, string fileName, Size? imageSize, WbsSettings settings)
        {
            try
            {
                int wbsWidth = wbs.Root.LayoutInfo.SubTreeSize.Width;
                int wbsHeight = wbs.Root.LayoutInfo.SubTreeSize.Height;
                if (null == imageSize || null == imageSize.Value)
                    imageSize = new Size(wbsWidth + 10, wbsHeight + 10);
                Image image = new Bitmap(imageSize.Value.Width, imageSize.Value.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

                // Five pixel margin in both directions
                int pageWidth = imageSize.Value.Width - 10;
                int pageHeight = imageSize.Value.Height - 10;

                float widthRatio = ((float)pageWidth) / ((float)wbsWidth);
                float heightRatio = ((float)pageHeight) / ((float)wbsHeight);
                float ratio = Math.Min(widthRatio, heightRatio);

                wbsHeight = (int)(((float)wbsHeight) * ratio);
                wbsWidth = (int)(((float)wbsWidth) * ratio);

                int leftOffset = (imageSize.Value.Width - wbsWidth) / 2;
                int topOffset = (imageSize.Value.Height - wbsHeight) / 2;

                Matrix matrix = new Matrix();
                matrix.Multiply(new Matrix(ratio, 0f, 0f, ratio, 0f, 0f), MatrixOrder.Append);
                matrix.Translate(leftOffset, topOffset, MatrixOrder.Append);

                bool checkPlausis = settings.CheckPlausis;
                settings.CheckPlausis = settings.PrintPlausis;

                try
                {
                    Graphics graphics = Graphics.FromImage(image);
                    graphics.FillRectangle(Brushes.White, 0, 0, imageSize.Value.Width, imageSize.Value.Height);
                    graphics.Transform = matrix;
                    WbsRenderer.RenderWbs(wbs.Root, settings, graphics, wbs, null, "");
                    image.Save(fileName);
                }
                finally
                {
                    settings.CheckPlausis = checkPlausis;
                }

                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("The following error occurred while exporting as an image:\n\n" + ex.Message,
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            return false;
        }

        public static bool ExportWbsAsEmf(WorkBreakdownStructure wbs, string fileName, Size? imageSize, WbsSettings settings, IntPtr referenceHdc)
        {
            try
            {
                int wbsWidth = wbs.Root.LayoutInfo.SubTreeSize.Width;
                int wbsHeight = wbs.Root.LayoutInfo.SubTreeSize.Height;
                if (null == imageSize || null == imageSize.Value)
                    imageSize = new Size(wbsWidth + 10, wbsHeight + 10);

                using (Image image = new System.Drawing.Imaging.Metafile(fileName, referenceHdc, new RectangleF(0f, 0f, imageSize.Value.Width, imageSize.Value.Height),
                    System.Drawing.Imaging.MetafileFrameUnit.Pixel, System.Drawing.Imaging.EmfType.EmfPlusDual))
                {
                    // Five pixel margin in both directions
                    int pageWidth = imageSize.Value.Width - 10;
                    int pageHeight = imageSize.Value.Height - 10;

                    float widthRatio = ((float)pageWidth) / ((float)wbsWidth);
                    float heightRatio = ((float)pageHeight) / ((float)wbsHeight);
                    float ratio = Math.Min(widthRatio, heightRatio);

                    wbsHeight = (int)(((float)wbsHeight) * ratio);
                    wbsWidth = (int)(((float)wbsWidth) * ratio);

                    int leftOffset = (imageSize.Value.Width - wbsWidth) / 2;
                    int topOffset = (imageSize.Value.Height - wbsHeight) / 2;

                    Matrix matrix = new Matrix();
                    matrix.Multiply(new Matrix(ratio, 0f, 0f, ratio, 0f, 0f), MatrixOrder.Append);
                    matrix.Translate(leftOffset, topOffset, MatrixOrder.Append);

                    bool checkPlausis = settings.CheckPlausis;
                    settings.CheckPlausis = settings.PrintPlausis;

                    try
                    {
                        using (Graphics graphics = Graphics.FromImage(image))
                        {
                            graphics.FillRectangle(Brushes.White, 0, 0, imageSize.Value.Width, imageSize.Value.Height);
                            graphics.Transform = matrix;
                            WbsRenderer.RenderWbs(wbs.Root, settings, graphics, wbs, null, "");
                        }
                        Clipboard.SetImage(image);
                    }
                    finally
                    {
                        settings.CheckPlausis = checkPlausis;
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("The following error occurred while exporting as an image:\n\n" + ex.Message,
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            return false;
        }

        public static void ExportWbsAsMpx(WorkBreakdownStructure wbs, string fileName)
        {
            using (StreamWriter writer = new StreamWriter(fileName, false, Encoding.Default))
            {
                writer.WriteLine(string.Format("MPX; WBS Editor {0}; 4.0", MainForm.VERSION));
                writer.WriteLine("10;EUR;0;2;.;,");
                writer.WriteLine("11;2;0;2;8;40");
                writer.WriteLine("61;1;3;40;44;72");

                ExportNodeAsMpx(wbs.Root, writer, 1);
            }
        }

        public static bool ExportWbsAsProject2007Xml(WorkBreakdownStructure wbs, string xmlFileName)
        {
            Project project = new Project();
            project.SaveVersion = "12"; // 2007
            project.DurationFormat = ProjectDurationFormat.Item7;
            project.DurationFormatSpecified = true;
            project.WorkFormat = ProjectWorkFormat.Item3;
            project.WorkFormatSpecified = true;
            project.DefaultFixedCostAccrual = ProjectDefaultFixedCostAccrual.Item1;
            project.DefaultFixedCostAccrualSpecified = true;
            project.DefaultTaskType = ProjectDefaultTaskType.Item1;

            List<ProjectTask> tasks = new List<ProjectTask>();
            int uid = 1;
            ExportNodeAsProjectXml(wbs.Root, tasks, 1, ref uid);
            project.Tasks = tasks.ToArray();

            XmlSerializer serializer = new XmlSerializer(typeof(Project));
            File.Delete(xmlFileName);
            using (FileStream fs = new FileStream(xmlFileName, FileMode.OpenOrCreate))
            {
                serializer.Serialize(fs, project);
            }
            return true;
        }

        public static bool CopyNodeToClipboard(WbsNode node)
        {
            try
            {
                string xmlString = WbsSerializer.SerializeNodeToString(node);
                if (null == xmlString)
                    return false;

                Clipboard.SetText(xmlString);

                return true;
            }
            catch (Exception /*ex*/)
            {
            }
            return false;
        }

        public static WbsNode GetNodeFromClipboard(WorkBreakdownStructure wbs)
        {
            try
            {
                if (!HasClipboardWbsNode())
                    return null;
                string xmlString = Clipboard.GetText();

                WbsNode node = WbsSerializer.DeserializeNodeFromString(xmlString, wbs);
                if (null == node)
                    return null;

                return node;
            }
            catch (Exception /*ex*/)
            {
            }
            return null;
        }

        public static bool HasClipboardWbsNode()
        {
            try
            {
                if (!Clipboard.ContainsText())
                    return false;
                string text = Clipboard.GetText();
                if (!text.Contains("<WbsXmlNode"))
                    return false;
                return true;
            }
            catch (Exception /*ex*/)
            {
            }
            return false;
        }
        #endregion

        #region Private Methods
        private static void ExtractWorkload(WbsNode wbsNode, Dictionary<string, PersonWorkload> workloadDictionary, string[] iterations)
        {
            if (!wbsNode.IsRoot)
            {
                string person = wbsNode.AssignedTo;
                if (string.IsNullOrEmpty(person))
                {
                    person = "(unassigned)";
                }
                PersonWorkload pw = null;
                if (workloadDictionary.ContainsKey(person))
                {
                    pw = workloadDictionary[person];
                }
                else
                {
                    pw = new PersonWorkload(person);
                    workloadDictionary[person] = pw;
                }
                bool includeNode = true;
                if (wbsNode.IsPublished &&
                    !(iterations.Length == 0))
                {
                    // Filter out tasks not in this iteration
                    //if (!wbsNode.Iteration.Equals(iterationFilter, StringComparison.CurrentCultureIgnoreCase))
                    //    includeNode = false;
                    if (!WbsGenerator.IsValidIteration(wbsNode.Iteration, iterations))
                        includeNode = false;
                }
                if (includeNode)
                {
                    pw.CompletedWork += wbsNode.CompletedWorkInDays;
                    pw.RemainingWork += wbsNode.RemainingWorkInDays;
                    if (wbsNode.IsLeaf)
                        pw.TasksAssigned++;
                }
            }

            if (wbsNode.HasChildNodes)
            {
                foreach (WbsNode childNode in wbsNode.ChildNodes)
                {
                    ExtractWorkload(childNode, workloadDictionary, iterations);
                }
            }
        }

        public static void CopyEstimationToClipboard(WorkBreakdownStructure wbs, float percentage, bool includeCompleted)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<table style=\"font-family:Calibri;font-size:10pt\">\n");

            ExtractEstimationRoot(wbs.Root, percentage, includeCompleted, sb);

            sb.Append("</table>\n");
            Clipboard.SetText(sb.ToString());
        }

        private static void ExtractEstimationRoot(WbsNode root, float percentage, bool includeCompleted, StringBuilder sb)
        {
            bool first = true;
            foreach (WbsNode child in root.ChildNodes)
            {
                if (!first)
                {
                    sb.Append("<tr><td>&nbsp;</td></tr>\n");
                }

                sb.Append("<tr style=\"background-color:#c0c0c0;\">");
                sb.AppendFormat("<td style=\"font-weight:bold;width:285px;\">{0}</td><td>&nbsp;</td>", child.Title);
                OutputEstimation(child, percentage, includeCompleted, sb);
                sb.Append("</tr>\n");

                foreach (WbsNode subChild in child.ChildNodes)
                {
                    OutputEstimationRow(subChild, percentage, includeCompleted, sb);
                }
                first = false;
            }
        }

        private static void OutputEstimationRow(WbsNode node, float percentage, bool includeCompleted, StringBuilder sb)
        {
            if (node.IsLeaf || node.RemainingWorkInDays > 0.0f)
            {
                sb.AppendFormat("<tr><td style=\"width:285px;\">{0}</td><td>{1}</td>", node.Title, ExtractStdPro(node.Title));
                OutputEstimation(node, percentage, includeCompleted, sb);
                sb.Append("</tr>\n");
            }
            foreach (WbsNode child in node.ChildNodes)
            {
                OutputEstimationRow(child, percentage, includeCompleted, sb);
            }
        }

        private static string ExtractStdPro(string p)
        {
            bool containsStd = false;
            bool containsPro = false;
            string temp = p.ToLower();
            containsStd = (temp.IndexOf("std") >= 0);
            containsPro = (temp.IndexOf("pro") >= 0);

            if (!containsStd && !containsPro)
                return "&nbsp;";
            else if (containsStd && !containsPro)
                return "std";
            else if (!containsStd && containsPro)
                return "pro";

            return "beide";
        }

        private static void OutputEstimation(WbsNode node, float percentage, bool includeCompleted, StringBuilder sb)
        {
            float work = node.RemainingWorkInDays;
            if (includeCompleted)
                work += node.CompletedWorkInDays;
            if (work > 0.004f)
            {
                float min = work * (100.0f - percentage) / 100.0f;
                float max = work * (100.0f + percentage) / 100.0f;
                float typ = work;
                sb.AppendFormat("<td>{0}</td><td>{1}</td><td>{2}</td><td>DEV</TD>",
                    min.ToString("#.00", NumberFormatInfo.CurrentInfo),
                    typ.ToString("#.00", NumberFormatInfo.CurrentInfo),
                    max.ToString("#.00", NumberFormatInfo.CurrentInfo));
            }
            else
            {
                sb.Append("<td></td><td></td><td></td><td></td>");
            }
        }

        private static void ExportNodeAsMpx(WbsNode node, StreamWriter writer, int depth)
        {
            if (node.IsLeaf)
            {
                int percentFinished = 0;
                if (node.RemainingWorkInDays + node.CompletedWorkInDays > 0.001)
                {
                    percentFinished = (int)(100.0 * node.CompletedWorkInDays / (node.RemainingWorkInDays + node.CompletedWorkInDays));
                }
                writer.WriteLine("70;\"{0}\";{1};{2};{3}%;\"{4}\"",
                    node.Title,
                    depth,
                    node.RemainingWorkInDays + node.CompletedWorkInDays,
                    percentFinished,
                    node.AssignedTo);
            }
            else
            {
                writer.WriteLine("70;\"{0}\";{1};;",
                    node.Title,
                    depth);
            }

            foreach (WbsNode child in node.ChildNodes)
            {
                ExportNodeAsMpx(child, writer, depth + 1);
            }
        }

        private static void ExportNodeAsProjectXml(WbsNode node, List<ProjectTask> tasks, int depth, ref int uid)
        {
            ProjectTask task = new ProjectTask();
            task.UID = uid.ToString();
            uid++;
            task.Name = node.Title;
            task.WBSLevel = depth.ToString();
            task.OutlineLevel = depth.ToString();
            task.FixedCostAccrual = ProjectTaskFixedCostAccrual.Item1;
            task.FixedCostAccrualSpecified = true;
            if (node.IsLeaf)
            {
                float totalTime = node.RemainingWorkInDays + node.CompletedWorkInDays;
                float completedTime = node.CompletedWorkInDays;
                float remainingTime = node.RemainingWorkInDays;
                int percentage = 0;
                if (completedTime > 0.01f)
                    percentage = (int)(100f * completedTime / totalTime);
                task.Duration = MakeTimeString(totalTime);
                task.Work = MakeTimeString(totalTime);
                task.PercentComplete = percentage.ToString();
                task.PercentWorkComplete = percentage.ToString();
                task.RemainingDuration = MakeTimeString(remainingTime);
                task.RemainingWork = MakeTimeString(remainingTime);
                task.ActualDuration = MakeTimeString(completedTime);
                task.ActualWork = MakeTimeString(completedTime);
            }
            tasks.Add(task);

            foreach (WbsNode child in node.ChildNodes)
            {
                ExportNodeAsProjectXml(child, tasks, depth + 1, ref uid);
            }
        }

        private static string MakeTimeString(float time)
        {
            float hoursPerDay = 8;
            int days = (int)time;
            time = (time - (float)days) * hoursPerDay;
            int hours = (int)time;
            time = (time - (float)hours) * 60f;
            int minutes = (int)time;

            string daysString = days > 0 ? "" + days : "";
            string hoursString = "";
            string minutesString = "";
            if (days > 0)
            {
                hoursString = "" + hours;
            }
            else
            {
                hoursString = hours > 0 ? "" + hours : "";
            }
            minutesString = "" + minutes;

            return string.Format("P{0}T{1}H{2}M0S", daysString, hoursString, minutesString);
        }
        #endregion
    }
}
